<?php
namespace Lib;
use Lib\Data\DataException;

class Data extends \ArrayObject{
    protected $_wasFrozen = false;
    protected $_wasCloned = false;
    
    /**
     * @var Encryption
     */
    protected $_encryptor = null;
    
    /**
     * @var Source
     */
    protected $_storage = null;
    
    const RETURN_AS_ARRAY = 10;
    const RETURN_AS_OBJECT = 20;
    
    public function __construct($array = array()){
        if(!self::is_array_accessible($array)){
            throw new DataException('Parameter $array must be array or NULL');
        }
        
        parent::__construct($array);
    }
    
    /**
     * 
     * Enter description here ...
     * @param Encryption | null $encryption
     */
    public function setEncryptor($encryptor = null){
        if(null === $encryptor || $encryptor instanceof Encryption){
            $this->_encryptor = $encryptor;
        } else {
            throw new DataException('You need to give NULL or Encription');
        }
    }
    
    /**
     * 
     * Enter description here ...
     * @param Source | null $storage
     */
    public function setStorage($storage = null){
        if($storage instanceof Source){
            $this->_storage = $storage;
            $this->_storage->setStorage(&$this);
        }
        if(null === $storage || $storage instanceof Source){
            $this->_storage = $storage;
        } else {
            throw new DataException('You need to give NULL or Source');
        }
        
        
    }
    /**
     * Retrieve current encryptor
     * 
     * @return Encryption
     */
    public function getEncryptor(){
        return $this->_encryptor;
    }
    
    /**
     * Retrieve current storage
     * 
     * @return Source
     */
    public function getStorage(){
        return $this->_storage;
    }
    
    /**
     * 
     * Enter description here ...
     */
    public function encodeAll(){
        if (null ===  $this->getEncryptor()){
            throw new EncryptorException('Encryptor is not setted');
        }
        
        if (!  $this->getEncryptor() instanceof Encryption){
            throw new EncryptorException('Encryptor must be instance of "Encryption"');
        }
        
         $this->getEncryptor()->encodeAll($this);
    }
    
    /**
     * 
     * Enter description here ...
     */
    public function decodeAll(){
        if (null === $this->getEncryptor()){
            throw new EncryptorException('Encryptor is not setted');
        }
        
        if (! $this->getEncryptor()  instanceof Encryption){
            throw new EncryptorException('Encryptor must be instance of "Encryption"');
        }
        
        $this->getEncryptor()->decodeAll($this);
    }
    /**
     * Sets value to element specified by name
     * 
     * @param string $name
     * @param mixed $value
     */
    public function setValue($name, $value){
        $this->offsetSet($name, $value);
    }
    
    /**
     * Retrieves value specified by $name
     * @param unknown_type $name
     */
    public function getValue($name){
        return $this->offsetGet($name);
    }
    
    /**
     * Sets multiple values
     * 
     * @param array $values
     * @throws DataException
     */
    public function setValues($values){
        if(!self::is_array_accessible($values)){
            throw new DataException('Variable $values must be array');
            return;
        }      
        foreach ($values as $name=>$value) {
            $this->setValue($name, $value);
        }
    }
    
    /**
     * Retrieves multiple values
     * 
     * @param array $names
     * @return Data | array
     * @throws DataException
     */
    public function getValues($names, $retAs = self::RETURN_AS_ARRAY){
        if(!self::is_array_accessible($values)){
            throw new DataException('Variable $values must be array');
            return;
        }
        
        $data = new Data();
        
        foreach ($names as $name){
            if(!$this->offsetExists($name)) continue;
            $value = $this->getValue($name);
            $data->setValue($name, $value);
        }
    
        switch ($retAs) {
            case self::RETURN_AS_ARRAY:
                return $data->toArray();
            break;
            case self::RETURN_AS_OBJECT:
                return $data;
            break;
            default:
                throw new DataException('Unknown type: '.$retAs);
            break;
        }
    }
    
    /**
     * Creates array representation of all parameters
     * 
     * @return array
     */
    public function toArray(){
        return $this->getArrayCopy();
    }
    
    /**
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value){
        $this->setValue($name, $value);
    }
    
    /**
     * @param string $name
     * @return mixed
     */
    public function __get($name){
        return $this->getValue($name);
    }
    
    /**
     * @param string $name
     * @return bool
     */
    public function __isset($name){
        return $this->offsetExists($name);
    }
    
    /**
     * @param string $name
     */
    public function __unset($name){
        $this->offsetUnset($name);
    }
    
    /**
     * @return void
     */
    public function __sleep(){
        $this->_wasFrozen = true;
        return;
    }
    
    /**
     * @return void
     */
    public function __clone(){
        $this->_wasFrozen = false;
        $this->_wasCloned = true;
        return;
    }
    
    
    public static function is_iteratable($a) { return is_array($a) || $a instanceof \Traversable; }
    public static function is_countable($a) { return is_array($a) || $a instanceof \Countable; }
    public static function is_array_accessible($a) { return (is_array($a) || $a instanceof \ArrayAccess); }
}

class EncryptorException extends \Exception{}

